﻿using System;
using System.Text.RegularExpressions;
using Cms.Application.IO;
using Cms.Domain;
using Cms.Domain.RepositoryInterfaces;
using Cms.Utilities;

namespace Cms.Application
{
    public class FileReferencesManager
    {
        private readonly Administrator _administrator;
        private readonly IContentRepository _contentRepository;
        private readonly IDiskAccess _diskAccess;
        private readonly ISiteRepository _siteRepository;

        public FileReferencesManager(Administrator administrator, ISiteRepository siteRepository, IContentRepository contentRepository, IDiskAccess diskAccess)
        {
            _administrator = administrator;
            _siteRepository = siteRepository;
            _contentRepository = contentRepository;
            _diskAccess = diskAccess;
        }

        public void RemoveFileReferences(string virtualPath, int siteId)
        {
            UpdateContentAndSourceCode(siteId, virtualPath, null, RemoveFileReferencesAction);
            Logging.LogInfo("Removed file references: " + virtualPath);
        }

        public void UpdateFileReferences(string oldVirtualPath, string newVirtualPath, int siteId)
        {
            UpdateContentAndSourceCode(siteId, oldVirtualPath, newVirtualPath, UpdateFileReferencesAction);
            Logging.LogInfo("Updated file references: " + oldVirtualPath + ", " + newVirtualPath);
        }
        
        private string RemoveFileReferencesAction(string code, string findPath, string replacement)
        {
            findPath = Regex.Escape(findPath);

            // Remove links to the file
            code = Regex.Replace(code, "(<a.*href=\")(" + findPath + ")([^>]*>.+</a>)", string.Empty, RegexOptions.IgnoreCase);

            // Remove references to the file from inline styles
            // To avoid wrong matches, the style attribute is included in the pattern. 
            // That makes it necessary to check the expression several times, as only one css-rule can be altered at once.
            string pattern = @"(style="".*?)([\w-]+[\s]*\:[\s]*)(url\([../]*)(" + findPath + @")(\))(;)([\s]*[^""]*"")";
            while (Regex.Matches(code, pattern, RegexOptions.IgnoreCase).Count > 0)
            {
                code = Regex.Replace(code, pattern, "$1$2none$6$7", RegexOptions.IgnoreCase);
            }

            // Remove empty style attributes
            code = Regex.Replace(code, @"[\s]*style=""""", string.Empty, RegexOptions.IgnoreCase);
            
            // Remove images using the file
            code = Regex.Replace(code, @"(<img[^>]*?src="".*?)(" + findPath + @")("".*?/>)", string.Empty, RegexOptions.IgnoreCase);
            
            // OnMouseOver
            code = Regex.Replace(
                code,
                @"(<img[^>]*)(onmouseover="".*?)(\s*this.src\s*=\s*')(" + findPath + @")(';{0,1})(.*?"")(.*?/>)", 
                "$1$2$6$7", 
                RegexOptions.IgnoreCase);
            
            // Remove empty onmouseover attributes
            code = Regex.Replace(code, @"[\s]*onmouseover=""""", string.Empty, RegexOptions.IgnoreCase);
            
            // OnMouseOut
            code = Regex.Replace(
                code,
                @"(<img[^>]*)(onmouseout="".*?)(\s*this.src\s*=\s*')(" + findPath + @")(';{0,1})(.*?"")(.*?/>)", 
                "$1$2$6$7", 
                RegexOptions.IgnoreCase);
            
            // Remove empty onmouseout attributes
            code = Regex.Replace(code, @"[\s]*onmouseout=""""", string.Empty, RegexOptions.IgnoreCase);
            
            // Remove references to the file from css code
            pattern = @"(background(-image){0,1}[\s]*\:.*)(url\([\s]*)('{0,1})((\.\./)*|/)(" + findPath + @")('{0,1})(\))(.*)(;)";
            while (Regex.Matches(code, pattern, RegexOptions.IgnoreCase).Count > 0)
            {
                code = Regex.Replace(code, pattern, "$1none$10$11", RegexOptions.IgnoreCase);
            }
            return code;
        }

        private string UpdateFileReferencesAction(string code, string findPath, string replacement)
        {
            findPath = Regex.Escape(findPath);

            // Update links to the file
            code = Regex.Replace(code, "(<a.*href=\")(" + findPath + ")([^>]*>.+</a>)", "$1" + replacement + "$3", RegexOptions.IgnoreCase);
            
            // Update references to the file in inline styles. 
            // To avoid wrong matches, the style attribute is included in the pattern. 
            // That makes it necessary to check the expression several times, as only one css-rule can be altered at once.
            string pattern = @"(style="".*?)([\w-]+[\s]*\:[\s]*url\([../]*)(" + findPath + @")(\);[\s]*)([^""]*"")";
            while (Regex.Matches(code, pattern, RegexOptions.IgnoreCase).Count > 0)
            {
                code = Regex.Replace(code, pattern, "$1$2" + replacement + "$4$5", RegexOptions.IgnoreCase);
            }

            // Update images using the file
            code = Regex.Replace(code, @"(<img[^>]*?src="".*?)(" + findPath + @")("".*?/>)", "$1" + replacement + "$3", RegexOptions.IgnoreCase);

            // OnMouseOver
            code = Regex.Replace(
                code,
                @"(<img[^>]*)(onmouseover="".*?)(\s*this.src\s*=\s*')(" + findPath + @")(';{0,1})(.*?"")(.*?/>)", 
                "$1$2$3" + replacement + "$5$6$7", 
                RegexOptions.IgnoreCase);

            // OnMouseOut
            code = Regex.Replace(
                code,
                @"(<img[^>]*)(onmouseout="".*?)(\s*this.src\s*=\s*')(" + findPath + @")(';{0,1})(.*?"")(.*?/>)", 
                "$1$2$3" + replacement + "$5$6$7", 
                RegexOptions.IgnoreCase);
            
            // Update references to the file from css code
            pattern = @"(background(-image){0,1}[\s]*\:.*)(url\([\s]*)('{0,1})((\.\./)*|/)(" + findPath + @")('{0,1})(\))(.*)(;)";
            while (Regex.Matches(code, pattern, RegexOptions.IgnoreCase).Count > 0)
            {
                code = Regex.Replace(code, pattern, "$1$3$4$5" + replacement + "$8$9$10$11", RegexOptions.IgnoreCase);
            }
            return code;
        }

        private void UpdateContentAndSourceCode(int siteId, string findPath, string replacement, FileReferenceActionDelegate action)
        {
            // Update content
            foreach (ContentTranslationVersion version in _contentRepository.GetContentWithReferencesTo(findPath, siteId))
            {
                version.Html = action(version.Html, findPath, replacement);
            }
            _siteRepository.SaveSite(_administrator.CurrentSite);

            // Update source code
            foreach (SourceCodeVersion version in _siteRepository.GetSourceCodeWithReferencesTo(findPath, siteId))
            {
                version.Code = action(version.Code, findPath, replacement);
                _siteRepository.SaveSite(_administrator.CurrentSite);
                if (version != version.Parent.CurrentVersion)
                {
                    continue;
                }
                foreach (MasterTemplate masterTemplate in version.Parent.Site.MasterTemplates)
                {
                    if (masterTemplate.SourceCode.ID == version.Parent.ID)
                    {
                        _diskAccess.DeleteFile("__System\\MasterTemplates\\" + masterTemplate.ID + ".master");
                        _diskAccess.CreateTextFile("__System\\MasterTemplates\\" + masterTemplate.ID + ".master", version.Parent.CurrentVersion.Code);
                    }
                }
                foreach (DetailTemplate detailTemplate in version.Parent.Site.DetailTemplates)
                {
                    if (detailTemplate.SourceCode.ID == version.Parent.ID)
                    {
                        _diskAccess.DeleteFile("__System\\DetailTemplates\\" + detailTemplate.ID + ".master");
                        _diskAccess.CreateTextFile("__System\\DetailTemplates\\" + detailTemplate.ID + ".master", version.Parent.CurrentVersion.Code);
                    }
                }
                foreach (Stylesheet stylesheet in version.Parent.Site.Stylesheets)
                {
                    if (stylesheet.SourceCode.ID == version.Parent.ID)
                    {
                        _diskAccess.DeleteFile("__System\\Stylesheets\\" + stylesheet.ID + ".css");
                        _diskAccess.CreateTextFile("__System\\Stylesheets\\" + stylesheet.ID + ".css", version.Parent.CurrentVersion.Code);
                    }
                }
                foreach (Page page in version.Parent.Site.Pages)
                {
                    if (page is ApplicationPage)
                    {
                        bool inUse = false;
                        foreach (var kvp in (page as ApplicationPage).Areas)
                        {
                            if (kvp.Value.ID == version.Parent.ID)
                            {
                                inUse = true;
                                break;
                            }
                        }
                        if (inUse)
                        {
                            string code = (page as ApplicationPage).SourceCode.CurrentVersion.Code;
                            foreach (var kvp in (page as ApplicationPage).Areas)
                            {
                                code += "<asp:Content ID=\"" + kvp.Key + "\" ContentPlaceHolderID=\"" + kvp.Key + "\" runat=\"server\">";
                                code += kvp.Value.CurrentVersion.Code;
                                code += "</asp:Content>" + Environment.NewLine;
                            }
                            _diskAccess.DeleteFile("__System\\ApplicationPages\\" + page.ID + ".aspx");
                            _diskAccess.CreateTextFile("__System\\ApplicationPages\\" + page.ID + ".aspx", code);
                        }
                    }
                }

                //TODO: Get affected AppletTemplates
            }
        }

        #region Nested type: FileReferenceActionDelegate

        private delegate string FileReferenceActionDelegate(string html, string findPath, string replacement);

        #endregion
    }
}